#!/usr/bin/perl

use strict;
use warnings;

use Test::More tests => 67;

BEGIN { use_ok('Heist::Parser'); }

my $parser = Heist::Parser->new(embedded_in_code =>  0);

{
  my $ast = $parser->ast_for(<<TEMPLATE);

well hello there

TEMPLATE

  ok(@{$ast->{subnodes}} == 1 && $ast->{subnodes}->[0], 'parsed single content node');
  isa_ok($ast->{subnodes}->[0], 'Heist::AST::Content', 'content node type');
  cmp_ok($ast->{subnodes}->[0]->text, 'eq', "\nwell hello there\n", 'text in content node');
}

{
  my $ast = $parser->ast_for(<<TEMPLATE);
a little content
: a line of code
and some more content
TEMPLATE

  ok(@{$ast->{subnodes}} == 3, 'content followed by code line followed by content has three lines');

  isa_ok($ast->{subnodes}->[0], 'Heist::AST::Content', 'content node type before code line');
  cmp_ok($ast->{subnodes}->[0]->text, 'eq', "a little content\n", 'text in content node before code line (including trailing newline)');

  isa_ok($ast->{subnodes}->[1], 'Heist::AST::CodeLines', 'code lines node correct type');
  cmp_ok($ast->{subnodes}->[1]->code, 'eq', " a line of code\n", 'got code line including trailing newline');

  isa_ok($ast->{subnodes}->[2], 'Heist::AST::Content', 'content node type after code line');
  cmp_ok($ast->{subnodes}->[2]->text, 'eq', 'and some more content', 'text in content node after code line');
}

{
  my $ast = $parser->ast_for(<<TEMPLATE);
 ...
: a line of code
: that runs into another
 ...
TEMPLATE

  ok(@{$ast->{subnodes}} == 3, 'content followed by code lines followed by content has three nodes');

  isa_ok($ast->{subnodes}->[0], 'Heist::AST::Content', 'content node type before code lines that run together');

  isa_ok($ast->{subnodes}->[1], 'Heist::AST::CodeLines', 'code lines node correct type when run together');
  cmp_ok($ast->{subnodes}->[1]->code, 'eq', " a line of code\n that runs into another\n", 'got code lines that run together including trailing newline');

  isa_ok($ast->{subnodes}->[2], 'Heist::AST::Content', 'content node type after code lines that run together');
}

{
  my $ast = $parser->ast_for('');

  ok(! @{$ast->{subnodes}}, 'empty template generates empty ast');
}

{
  my $ast = $parser->ast_for(<<TEMPLATE);
: just code line
TEMPLATE

  ok(@{$ast->{subnodes}} == 1, 'single code line returns one node');
  isa_ok($ast->{subnodes}->[0], 'Heist::AST::CodeLines', 'got a code single code line');
}

{
  my $ast = $parser->ast_for(<<TEMPLATE);
<( an expression )>
TEMPLATE

  ok(@{$ast->{subnodes}} == 1, 'single expression tag returns one node');
  isa_ok($ast->{subnodes}->[0], 'Heist::AST::ExpressionTag', 'got single expression tag');
  cmp_ok($ast->{subnodes}->[0]->code, 'eq', ' an expression ', 'contents of expression tag');
}

{
  my $ast = $parser->ast_for(<<TEMPLATE);
<(exp1)><(exp2)>
TEMPLATE

  ok(@{$ast->{subnodes}} == 2, 'adjacent expression tags do not run together');
  isa_ok($ast->{subnodes}->[0], 'Heist::AST::ExpressionTag', 'first adjacent expression tag type');
  cmp_ok($ast->{subnodes}->[0]->code, 'eq', 'exp1', 'first adjacent expression tag code');
  isa_ok($ast->{subnodes}->[1], 'Heist::AST::ExpressionTag', 'second adjacent expression tag type');
  cmp_ok($ast->{subnodes}->[1]->code, 'eq', 'exp2', 'second adjacent expression tag code');
}

{
  my $ast = $parser->ast_for(<<TEMPLATE);
<[ any old code ]>
TEMPLATE

  ok(@{$ast->{subnodes}} == 1, 'single code tag returns one node');
  isa_ok($ast->{subnodes}->[0], 'Heist::AST::CodeTag', 'got single code tag');
  cmp_ok($ast->{subnodes}->[0]->code, 'eq', ' any old code ', 'contents of code tag');
}

{
  my $ast = $parser->ast_for(<<TEMPLATE);
<[code1]><[code2]>
TEMPLATE

  ok(@{$ast->{subnodes}} == 1, 'adjacent code tags do run together');
  isa_ok($ast->{subnodes}->[0], 'Heist::AST::CodeTag', 'ran together code tag type');
  cmp_ok($ast->{subnodes}->[0]->code, 'eq', 'code1code2', 'ran together code tag code');
}

eval { $parser->ast_for(<<TEMPLATE) };

   <( )>

TEMPLATE

ok($@, 'empty expression produces error');
ok($@ =~ /empty expression tag/osi, 'error is descriptive of problem');
ok($@ =~ /at line 2, character 4/osi, 'line and char info on empty expression error');

{
  my $ast = $parser->ast_for(<<TEMPLATE);
 displayed content |- not displayed content
TEMPLATE

  ok(@{$ast->{subnodes}} == 3, 'template including output stop contains three nodes');

  isa_ok($ast->{subnodes}->[0], 'Heist::AST::Content', 'content before output stop is content node');
  cmp_ok($ast->{subnodes}->[0]->text, 'eq', ' displayed content ', 'content node before stop has correct content');

  isa_ok($ast->{subnodes}->[1], 'Heist::AST::OutputStop', 'output stop is correct type');

  isa_ok($ast->{subnodes}->[2], 'Heist::AST::IgnoredContent', 'content before output stop is ignored content node');
  cmp_ok($ast->{subnodes}->[2]->text, 'eq', ' not displayed content', 'content after output stop is retained and correct');
};

eval { $parser->ast_for(<<TEMPLATE) };
-|
TEMPLATE

ok($@, 'output start where output already on raises error');
ok($@ =~ /output already on/osi, 'output start when already on error descriptive');
ok($@ =~ /at line 1, character 1/osi, 'output start when already on error has correct position');

{
  my $ast = $parser->ast_for(<<TEMPLATE);
displayed |- not displayed -| displayed
TEMPLATE

  ok(@{$ast->{subnodes}} == 5, 'template including output stop and start contains five nodes');

  isa_ok($ast->{subnodes}->[0], 'Heist::AST::Content', 'content before output stop is content node');
  cmp_ok($ast->{subnodes}->[0]->text, 'eq', 'displayed ', 'content node before stop has correct content');

  isa_ok($ast->{subnodes}->[1], 'Heist::AST::OutputStop', 'output stop is correct type');


  isa_ok($ast->{subnodes}->[2], 'Heist::AST::IgnoredContent', 'content between output stop and output start is ignored content node');
  cmp_ok($ast->{subnodes}->[2]->text, 'eq', ' not displayed ', 'content between output stop and output start is retained and correct');

  isa_ok($ast->{subnodes}->[3], 'Heist::AST::OutputStart', 'output start is correct type');

  isa_ok($ast->{subnodes}->[4], 'Heist::AST::Content', 'content after output start is content node');
  cmp_ok($ast->{subnodes}->[4]->text, 'eq', ' displayed', 'content node after output start has correct content');
};

{
  my $ast = $parser->ast_for(<<TEMPLATE);
<~my_method>method content</~>
TEMPLATE

  ok(@{$ast->{subnodes}} == 1, 'method has single method node');

  my $method_node = $ast->{subnodes}->[0];
  isa_ok($method_node, 'Heist::AST::Method', 'method creates method node');
  ok(! defined $method_node->argument_list, 'argument list not defined when not specified');

  ok(@{$method_node->{subnodes}} == 1, 'method has one subnode');

  isa_ok($method_node->{subnodes}->[0], 'Heist::AST::Content', 'content in method is a content node');
  cmp_ok($method_node->{subnodes}->[0]->text, 'eq', 'method content', 'content inside method contains right content');
}

eval { $parser->ast_for(' <~anything>') };

ok($@, 'unclosed method tag results in error');
ok($@ =~ /\<\~anything\.\.\.> tag without corresponding \<\/\~\> closing tag/osi, 'unclosed method tag results in descriptive error');
ok($@ =~ /at line 1, character 2/osi, 'tag position in unclosed method tag is correct');

eval { $parser->ast_for('<~blah></~blah>') };

ok(! $@, 'matching close tag okay');

eval { $parser->ast_for('
<~foo>
</~bar>
') };

ok($@, 'mismatch of closing tag causes parse error');
ok($@ =~ /mismatched closing tag \<\/\~bar\> for \<\~foo\.\.\.> tag/, 'mismatched closing tag results in descriptive error');
ok($@ =~ /at line 3, character 1/, 'error reports correct position');



